{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Use case proposed in [issue #78](https://github.com/bsorrentino/langgraph4j/issues/93) by [tansice](https://github.com/tansice)\n",
    "\n",
    "## There is a null pointer issue in ToolExecutionRequestSerializer."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "String userHomeDir = System.getProperty(\"user.home\");\n",
    "String localRespoUrl = \"file://\" + userHomeDir + \"/.m2/repository/\";\n",
    "String langchain4jVersion = \"1.0.0-beta2\";\n",
    "String langgraph4jVersion = \"1.5-SNAPSHOT\";"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%bash \n",
    "rm -rf \\{userHomeDir}/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/org/bsc/langgraph4j"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[0mRepository \u001b[1m\u001b[32mlocal\u001b[0m url: \u001b[1m\u001b[32mfile:///Users/bsorrentino/.m2/repository/\u001b[0m added.\n",
      "\u001b[0mAdding dependency \u001b[0m\u001b[1m\u001b[32morg.slf4j:slf4j-jdk14:2.0.9\n",
      "\u001b[0mAdding dependency \u001b[0m\u001b[1m\u001b[32morg.bsc.langgraph4j:langgraph4j-core:1.4-SNAPSHOT\n",
      "\u001b[0mAdding dependency \u001b[0m\u001b[1m\u001b[32morg.bsc.langgraph4j:langgraph4j-langchain4j:1.4-SNAPSHOT\n",
      "\u001b[0mAdding dependency \u001b[0m\u001b[1m\u001b[32morg.bsc.langgraph4j:langgraph4j-agent-executor:1.4-SNAPSHOT\n",
      "\u001b[0mAdding dependency \u001b[0m\u001b[1m\u001b[32mdev.langchain4j:langchain4j:1.0.0-beta2\n",
      "\u001b[0mAdding dependency \u001b[0m\u001b[1m\u001b[32mdev.langchain4j:langchain4j-ollama:1.0.0-beta2\n",
      "\u001b[0mSolving dependencies\n",
      "Resolved artifacts count: 55\n",
      "Add to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/org/slf4j/slf4j-jdk14/2.0.9/slf4j-jdk14-2.0.9.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/org/slf4j/slf4j-api/2.0.9/slf4j-api-2.0.9.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/org/bsc/langgraph4j/langgraph4j-core/1.4-SNAPSHOT/langgraph4j-core-1.4-SNAPSHOT.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/org/bsc/async/async-generator/3.0.0/async-generator-3.0.0.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/org/bsc/langgraph4j/langgraph4j-langchain4j/1.4-SNAPSHOT/langgraph4j-langchain4j-1.4-SNAPSHOT.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/org/bsc/langgraph4j/langgraph4j-agent-executor/1.4-SNAPSHOT/langgraph4j-agent-executor-1.4-SNAPSHOT.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/dev/langchain4j/langchain4j-open-ai/1.0.0-beta2/langchain4j-open-ai-1.0.0-beta2.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/com/knuddels/jtokkit/1.1.0/jtokkit-1.1.0.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/dev/langchain4j/langchain4j-azure-open-ai/1.0.0-beta2/langchain4j-azure-open-ai-1.0.0-beta2.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/com/azure/azure-ai-openai/1.0.0-beta.14/azure-ai-openai-1.0.0-beta.14.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/com/azure/azure-core/1.55.2/azure-core-1.55.2.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/com/azure/azure-json/1.4.0/azure-json-1.4.0.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/com/azure/azure-xml/1.2.0/azure-xml-1.2.0.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.17.2/jackson-datatype-jsr310-2.17.2.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/io/projectreactor/reactor-core/3.4.41/reactor-core-3.4.41.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/org/reactivestreams/reactive-streams/1.0.4/reactive-streams-1.0.4.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/com/azure/azure-core-http-netty/1.15.10/azure-core-http-netty-1.15.10.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/io/netty/netty-handler/4.1.118.Final/netty-handler-4.1.118.Final.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/io/netty/netty-resolver/4.1.118.Final/netty-resolver-4.1.118.Final.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/io/netty/netty-transport/4.1.118.Final/netty-transport-4.1.118.Final.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/io/netty/netty-handler-proxy/4.1.118.Final/netty-handler-proxy-4.1.118.Final.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/io/netty/netty-codec-socks/4.1.118.Final/netty-codec-socks-4.1.118.Final.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/io/netty/netty-buffer/4.1.118.Final/netty-buffer-4.1.118.Final.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/io/netty/netty-codec/4.1.118.Final/netty-codec-4.1.118.Final.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/io/netty/netty-codec-http/4.1.118.Final/netty-codec-http-4.1.118.Final.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/io/netty/netty-codec-http2/4.1.118.Final/netty-codec-http2-4.1.118.Final.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/io/netty/netty-transport-native-unix-common/4.1.118.Final/netty-transport-native-unix-common-4.1.118.Final.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/io/netty/netty-transport-native-epoll/4.1.118.Final/netty-transport-native-epoll-4.1.118.Final-linux-x86_64.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/io/netty/netty-transport-classes-epoll/4.1.118.Final/netty-transport-classes-epoll-4.1.118.Final.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/io/netty/netty-transport-native-kqueue/4.1.118.Final/netty-transport-native-kqueue-4.1.118.Final-osx-x86_64.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/io/netty/netty-transport-classes-kqueue/4.1.118.Final/netty-transport-classes-kqueue-4.1.118.Final.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/io/netty/netty-tcnative-boringssl-static/2.0.70.Final/netty-tcnative-boringssl-static-2.0.70.Final.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/io/netty/netty-tcnative-classes/2.0.70.Final/netty-tcnative-classes-2.0.70.Final.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/io/netty/netty-tcnative-boringssl-static/2.0.70.Final/netty-tcnative-boringssl-static-2.0.70.Final-linux-x86_64.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/io/netty/netty-tcnative-boringssl-static/2.0.70.Final/netty-tcnative-boringssl-static-2.0.70.Final-linux-aarch_64.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/io/netty/netty-tcnative-boringssl-static/2.0.70.Final/netty-tcnative-boringssl-static-2.0.70.Final-osx-x86_64.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/io/netty/netty-tcnative-boringssl-static/2.0.70.Final/netty-tcnative-boringssl-static-2.0.70.Final-osx-aarch_64.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/io/netty/netty-tcnative-boringssl-static/2.0.70.Final/netty-tcnative-boringssl-static-2.0.70.Final-windows-x86_64.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/io/projectreactor/netty/reactor-netty-http/1.0.48/reactor-netty-http-1.0.48.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/io/netty/netty-resolver-dns/4.1.112.Final/netty-resolver-dns-4.1.112.Final.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/io/netty/netty-codec-dns/4.1.112.Final/netty-codec-dns-4.1.112.Final.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/io/netty/netty-resolver-dns-native-macos/4.1.112.Final/netty-resolver-dns-native-macos-4.1.112.Final-osx-x86_64.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/io/netty/netty-resolver-dns-classes-macos/4.1.112.Final/netty-resolver-dns-classes-macos-4.1.112.Final.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/io/projectreactor/netty/reactor-netty-core/1.0.48/reactor-netty-core-1.0.48.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/io/netty/netty-common/4.1.118.Final/netty-common-4.1.118.Final.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/dev/langchain4j/langchain4j/1.0.0-beta2/langchain4j-1.0.0-beta2.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/dev/langchain4j/langchain4j-core/1.0.0-beta2/langchain4j-core-1.0.0-beta2.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/org/jspecify/jspecify/1.0.0/jspecify-1.0.0.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/org/apache/opennlp/opennlp-tools/1.9.4/opennlp-tools-1.9.4.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/dev/langchain4j/langchain4j-ollama/1.0.0-beta2/langchain4j-ollama-1.0.0-beta2.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/dev/langchain4j/langchain4j-http-client-jdk/1.0.0-beta2/langchain4j-http-client-jdk-1.0.0-beta2.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/dev/langchain4j/langchain4j-http-client/1.0.0-beta2/langchain4j-http-client-1.0.0-beta2.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/com/fasterxml/jackson/core/jackson-databind/2.18.2/jackson-databind-2.18.2.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/com/fasterxml/jackson/core/jackson-annotations/2.18.2/jackson-annotations-2.18.2.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/com/fasterxml/jackson/core/jackson-core/2.18.2/jackson-core-2.18.2.jar\u001b[0m\n",
      "\u001b[0m"
     ]
    }
   ],
   "source": [
    "%dependency /add-repo local \\{localRespoUrl} release|never snapshot|always\n",
    "// %dependency /list-repos\n",
    "%dependency /add org.slf4j:slf4j-jdk14:2.0.9\n",
    "%dependency /add org.bsc.langgraph4j:langgraph4j-core:\\{langgraph4jVersion}\n",
    "%dependency /add org.bsc.langgraph4j:langgraph4j-langchain4j:\\{langgraph4jVersion}\n",
    "%dependency /add org.bsc.langgraph4j:langgraph4j-agent-executor:\\{langgraph4jVersion}\n",
    "%dependency /add dev.langchain4j:langchain4j:\\{langchain4jVersion}\n",
    "%dependency /add dev.langchain4j:langchain4j-ollama:\\{langchain4jVersion}\n",
    "\n",
    "%dependency /resolve"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Initialize Logger**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "try( var file = new java.io.FileInputStream(\"./logging.properties\")) {\n",
    "    var lm = java.util.logging.LogManager.getLogManager();\n",
    "    lm.checkAccess(); \n",
    "    lm.readConfiguration( file );\n",
    "}\n",
    "\n",
    "var log = org.slf4j.LoggerFactory.getLogger(\"default\");\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "import dev.langchain4j.agent.tool.P;\n",
    "import dev.langchain4j.agent.tool.Tool;\n",
    "import dev.langchain4j.data.message.AiMessage;\n",
    "import dev.langchain4j.data.message.ChatMessage;\n",
    "import dev.langchain4j.data.message.UserMessage;\n",
    "import dev.langchain4j.model.chat.StreamingChatLanguageModel;\n",
    "import dev.langchain4j.model.chat.request.ChatRequest;\n",
    "import dev.langchain4j.model.chat.request.ChatRequestParameters;\n",
    "import dev.langchain4j.model.ollama.OllamaStreamingChatModel;\n",
    "import org.bsc.langgraph4j.action.AsyncEdgeAction;\n",
    "import org.bsc.langgraph4j.action.AsyncNodeAction;\n",
    "import org.bsc.langgraph4j.action.EdgeAction;\n",
    "import org.bsc.langgraph4j.action.NodeAction;\n",
    "import org.bsc.langgraph4j.langchain4j.generators.StreamingChatGenerator;\n",
    "import org.bsc.langgraph4j.langchain4j.serializer.std.ChatMesssageSerializer;\n",
    "import org.bsc.langgraph4j.langchain4j.serializer.std.ToolExecutionRequestSerializer;\n",
    "import org.bsc.langgraph4j.langchain4j.tool.ToolNode;\n",
    "import org.bsc.langgraph4j.prebuilt.MessagesState;\n",
    "import org.bsc.langgraph4j.prebuilt.MessagesStateGraph;\n",
    "import org.bsc.langgraph4j.serializer.std.ObjectStreamStateSerializer;\n",
    "import org.slf4j.Logger;\n",
    "import org.slf4j.LoggerFactory;\n",
    "\n",
    "import java.util.Map;\n",
    "\n",
    "import static org.bsc.langgraph4j.StateGraph.END;\n",
    "import static org.bsc.langgraph4j.StateGraph.START;\n",
    "import static org.bsc.langgraph4j.action.AsyncEdgeAction.edge_async;\n",
    "import static org.bsc.langgraph4j.action.AsyncNodeAction.node_async;\n",
    "\n",
    "static class SearchTool {\n",
    "    @Tool(\"Use to surf the web, fetch current information, check the weather, and retrieve other information.\")\n",
    "    String execQuery(@P(\"The query to use in your search.\") String query) {\n",
    "        return \"Cold, with a low of 13 degrees\";\n",
    "    }\n",
    "}\n",
    "\n",
    "void testToolsStreamingChat( String modelName ) throws Exception {\n",
    "    // Setup streaming model\n",
    "    var model = OllamaStreamingChatModel.builder()\n",
    "            .baseUrl(\"http://localhost:11434\")\n",
    "            .temperature(0.5)\n",
    "            .logRequests(true)\n",
    "            .logResponses(true)\n",
    "            .modelName( modelName )\n",
    "            //.modelName(\"qwen2.5:7b\")\n",
    "            //.modelName(\"llama3.1:latest\")\n",
    "            .build();\n",
    "\n",
    "    // Setup tools\n",
    "    var tools = ToolNode.builder()\n",
    "            .specification(new SearchTool())\n",
    "            .build();\n",
    "\n",
    "    // Setup serializers\n",
    "    ObjectStreamStateSerializer<MessagesState<ChatMessage>> stateSerializer = new ObjectStreamStateSerializer<>(MessagesState::new);\n",
    "    stateSerializer.mapper()\n",
    "            .register(dev.langchain4j.agent.tool.ToolExecutionRequest.class, new ToolExecutionRequestSerializer())\n",
    "            .register(ChatMessage.class, new ChatMesssageSerializer());\n",
    "\n",
    "    // Define graph\n",
    "    NodeAction<MessagesState<ChatMessage>> callModel = state -> {\n",
    "        var generator = StreamingChatGenerator.<MessagesState<ChatMessage>>builder()\n",
    "                .mapResult(response -> Map.of(\"messages\", response.aiMessage()))\n",
    "                .startingNode(\"agent\")\n",
    "                .startingState(state)\n",
    "                .build();\n",
    "\n",
    "        var parameters = ChatRequestParameters.builder()\n",
    "                .toolSpecifications(tools.toolSpecifications())\n",
    "                .build();\n",
    "        var request = ChatRequest.builder()\n",
    "                .messages(state.messages())\n",
    "                .parameters(parameters)\n",
    "                .build();\n",
    "\n",
    "        model.chat(request, generator.handler());\n",
    "\n",
    "        return Map.of(\"_streaming_messages\", generator);\n",
    "    };\n",
    "\n",
    "    EdgeAction<MessagesState<ChatMessage>> routeMessage = state -> {\n",
    "        var lastMessage = state.lastMessage()\n",
    "                .orElseThrow(() -> new IllegalStateException(\"last message not found!\"));\n",
    "\n",
    "        if (lastMessage instanceof AiMessage message) {\n",
    "            if (message.hasToolExecutionRequests()) {\n",
    "                return \"next\";\n",
    "            }\n",
    "        }\n",
    "\n",
    "        return \"exit\";\n",
    "    };\n",
    "\n",
    "    NodeAction<MessagesState<ChatMessage>> invokeTool = state -> {\n",
    "        var lastMessage = state.lastMessage()\n",
    "                .orElseThrow(() -> new IllegalStateException(\"last message not found!\"));\n",
    "\n",
    "        if (lastMessage instanceof AiMessage lastAiMessage) {\n",
    "            var result = tools.execute(lastAiMessage.toolExecutionRequests(), null)\n",
    "                    .orElseThrow(() -> new IllegalStateException(\"no tool found!\"));\n",
    "\n",
    "            return Map.of(\"messages\", result);\n",
    "        }\n",
    "\n",
    "        throw new IllegalStateException(\"invalid last message\");\n",
    "    };\n",
    "\n",
    "    var workflow = new MessagesStateGraph<>(stateSerializer)\n",
    "            .addNode(\"agent\", node_async(callModel))\n",
    "            .addNode(\"tools\", node_async(invokeTool))\n",
    "            .addEdge(START, \"agent\")\n",
    "            .addConditionalEdges(\"agent\",\n",
    "                    edge_async(routeMessage),\n",
    "                    Map.of(\"next\", \"tools\", \"exit\", END))\n",
    "            .addEdge(\"tools\", \"agent\");\n",
    "\n",
    "    var app = workflow.compile();\n",
    "\n",
    "    var output = app.stream(Map.of(\"messages\", UserMessage.from(\"How is the weather in New York today?\")));\n",
    "    for (var out : output) {\n",
    "        log.info(\"StreamingOutput: {}\", out);\n",
    "    }\n",
    "}\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "START \n",
      "StreamingOutput: NodeOutput{node=__START__, state={messages=[UserMessage { name = null contents = [TextContent { text = \"How is the weather in New York today?\" }] }]}} \n",
      "StreamingOutput: StreamingOutput{node=agent, state={messages=[UserMessage { name = null contents = [TextContent { text = \"How is the weather in New York today?\" }] }]}, chunk=} \n",
      "ToolExecutionRequest id is null! \n",
      "StreamingOutput: StreamingOutput{node=agent, state={messages=[UserMessage { name = null contents = [TextContent { text = \"How is the weather in New York today?\" }] }]}, chunk=} \n",
      "ToolExecutionRequest id is null! \n",
      "execute: execQuery \n",
      "ToolExecutionRequest id is null! \n",
      "ToolExecutionResultMessage id is null! \n",
      "StreamingOutput: NodeOutput{node=agent, state={messages=[AiMessage { text = null toolExecutionRequests = [ToolExecutionRequest { id = null, name = \"execQuery\", arguments = \"{\n",
      "  \"query\" : \"weather in New York today\"\n",
      "}\" }] }]}} \n",
      "ToolExecutionRequest id is null! \n",
      "ToolExecutionResultMessage id is null! \n",
      "StreamingOutput: NodeOutput{node=tools, state={messages=[AiMessage { text = null toolExecutionRequests = [ToolExecutionRequest { id = null, name = \"execQuery\", arguments = \"{\n",
      "  \"query\" : \"weather in New York today\"\n",
      "}\" }] }, ToolExecutionResultMessage { id = null toolName = \"execQuery\" text = \"Cold, with a low of 13 degrees\" }]}} \n",
      "StreamingOutput: StreamingOutput{node=agent, state={messages=[AiMessage { text = null toolExecutionRequests = [ToolExecutionRequest { id = null, name = \"execQuery\", arguments = \"{\n",
      "  \"query\" : \"weather in New York today\"\n",
      "}\" }] }, ToolExecutionResultMessage { id = null toolName = \"execQuery\" text = \"Cold, with a low of 13 degrees\" }]}, chunk=The current weather in New York is cold, with the temperature expected to drop to a low of 13 degrees.} \n",
      "StreamingOutput: NodeOutput{node=agent, state={messages=[AiMessage { text = \"The current weather in New York is cold, with the temperature expected to drop to a low of 13 degrees.\" toolExecutionRequests = null }]}} \n",
      "StreamingOutput: NodeOutput{node=__END__, state={messages=[AiMessage { text = \"The current weather in New York is cold, with the temperature expected to drop to a low of 13 degrees.\" toolExecutionRequests = null }]}} \n"
     ]
    }
   ],
   "source": [
    "testToolsStreamingChat(\"qwen2.5:7b\");"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "START \n",
      "StreamingOutput: NodeOutput{node=__START__, state={messages=[UserMessage { name = null contents = [TextContent { text = \"How is the weather in New York today?\" }] }]}} \n",
      "StreamingOutput: StreamingOutput{node=agent, state={messages=[UserMessage { name = null contents = [TextContent { text = \"How is the weather in New York today?\" }] }]}, chunk=} \n",
      "ToolExecutionRequest id is null! \n",
      "StreamingOutput: StreamingOutput{node=agent, state={messages=[UserMessage { name = null contents = [TextContent { text = \"How is the weather in New York today?\" }] }]}, chunk=} \n",
      "ToolExecutionRequest id is null! \n",
      "execute: execQuery \n",
      "ToolExecutionRequest id is null! \n",
      "ToolExecutionResultMessage id is null! \n",
      "StreamingOutput: NodeOutput{node=agent, state={messages=[AiMessage { text = null toolExecutionRequests = [ToolExecutionRequest { id = null, name = \"execQuery\", arguments = \"{\n",
      "  \"query\" : \"weather in New York today\"\n",
      "}\" }] }]}} \n",
      "ToolExecutionRequest id is null! \n",
      "ToolExecutionResultMessage id is null! \n",
      "StreamingOutput: NodeOutput{node=tools, state={messages=[AiMessage { text = null toolExecutionRequests = [ToolExecutionRequest { id = null, name = \"execQuery\", arguments = \"{\n",
      "  \"query\" : \"weather in New York today\"\n",
      "}\" }] }, ToolExecutionResultMessage { id = null toolName = \"execQuery\" text = \"Cold, with a low of 13 degrees\" }]}} \n",
      "StreamingOutput: StreamingOutput{node=agent, state={messages=[AiMessage { text = null toolExecutionRequests = [ToolExecutionRequest { id = null, name = \"execQuery\", arguments = \"{\n",
      "  \"query\" : \"weather in New York today\"\n",
      "}\" }] }, ToolExecutionResultMessage { id = null toolName = \"execQuery\" text = \"Cold, with a low of 13 degrees\" }]}, chunk=} \n",
      "ToolExecutionRequest id is null! \n",
      "StreamingOutput: StreamingOutput{node=agent, state={messages=[AiMessage { text = null toolExecutionRequests = [ToolExecutionRequest { id = null, name = \"execQuery\", arguments = \"{\n",
      "  \"query\" : \"weather in New York today\"\n",
      "}\" }] }, ToolExecutionResultMessage { id = null toolName = \"execQuery\" text = \"Cold, with a low of 13 degrees\" }]}, chunk=} \n",
      "ToolExecutionRequest id is null! \n",
      "execute: execQuery \n",
      "ToolExecutionRequest id is null! \n",
      "ToolExecutionResultMessage id is null! \n",
      "StreamingOutput: NodeOutput{node=agent, state={messages=[AiMessage { text = null toolExecutionRequests = [ToolExecutionRequest { id = null, name = \"execQuery\", arguments = \"{\n",
      "  \"query\" : \"New York weather forecast for the next 5 days\"\n",
      "}\" }] }]}} \n",
      "ToolExecutionRequest id is null! \n",
      "ToolExecutionResultMessage id is null! \n",
      "StreamingOutput: NodeOutput{node=tools, state={messages=[AiMessage { text = null toolExecutionRequests = [ToolExecutionRequest { id = null, name = \"execQuery\", arguments = \"{\n",
      "  \"query\" : \"New York weather forecast for the next 5 days\"\n",
      "}\" }] }, ToolExecutionResultMessage { id = null toolName = \"execQuery\" text = \"Cold, with a low of 13 degrees\" }]}} \n",
      "StreamingOutput: StreamingOutput{node=agent, state={messages=[AiMessage { text = null toolExecutionRequests = [ToolExecutionRequest { id = null, name = \"execQuery\", arguments = \"{\n",
      "  \"query\" : \"New York weather forecast for the next 5 days\"\n",
      "}\" }] }, ToolExecutionResultMessage { id = null toolName = \"execQuery\" text = \"Cold, with a low of 13 degrees\" }]}, chunk=} \n",
      "ToolExecutionRequest id is null! \n",
      "StreamingOutput: StreamingOutput{node=agent, state={messages=[AiMessage { text = null toolExecutionRequests = [ToolExecutionRequest { id = null, name = \"execQuery\", arguments = \"{\n",
      "  \"query\" : \"New York weather forecast for the next 5 days\"\n",
      "}\" }] }, ToolExecutionResultMessage { id = null toolName = \"execQuery\" text = \"Cold, with a low of 13 degrees\" }]}, chunk=} \n",
      "ToolExecutionRequest id is null! \n",
      "execute: execQuery \n",
      "ToolExecutionRequest id is null! \n",
      "ToolExecutionResultMessage id is null! \n",
      "StreamingOutput: NodeOutput{node=agent, state={messages=[AiMessage { text = null toolExecutionRequests = [ToolExecutionRequest { id = null, name = \"execQuery\", arguments = \"{\n",
      "  \"query\" : \"London weather forecast for the next 5 days\"\n",
      "}\" }] }]}} \n",
      "ToolExecutionRequest id is null! \n",
      "ToolExecutionResultMessage id is null! \n",
      "StreamingOutput: NodeOutput{node=tools, state={messages=[AiMessage { text = null toolExecutionRequests = [ToolExecutionRequest { id = null, name = \"execQuery\", arguments = \"{\n",
      "  \"query\" : \"London weather forecast for the next 5 days\"\n",
      "}\" }] }, ToolExecutionResultMessage { id = null toolName = \"execQuery\" text = \"Cold, with a low of 13 degrees\" }]}} \n",
      "StreamingOutput: StreamingOutput{node=agent, state={messages=[AiMessage { text = null toolExecutionRequests = [ToolExecutionRequest { id = null, name = \"execQuery\", arguments = \"{\n",
      "  \"query\" : \"London weather forecast for the next 5 days\"\n",
      "}\" }] }, ToolExecutionResultMessage { id = null toolName = \"execQuery\" text = \"Cold, with a low of 13 degrees\" }]}, chunk=} \n",
      "ToolExecutionRequest id is null! \n",
      "StreamingOutput: StreamingOutput{node=agent, state={messages=[AiMessage { text = null toolExecutionRequests = [ToolExecutionRequest { id = null, name = \"execQuery\", arguments = \"{\n",
      "  \"query\" : \"London weather forecast for the next 5 days\"\n",
      "}\" }] }, ToolExecutionResultMessage { id = null toolName = \"execQuery\" text = \"Cold, with a low of 13 degrees\" }]}, chunk=} \n",
      "ToolExecutionRequest id is null! \n",
      "execute: getCurrencyRate \n",
      "java.lang.IllegalStateException: no tool found! \n",
      "java.util.concurrent.ExecutionException: java.lang.IllegalStateException: no tool found!\n",
      "\tat java.base/java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:396)\n",
      "\tat java.base/java.util.concurrent.CompletableFuture.get(CompletableFuture.java:2073)\n",
      "\tat org.bsc.langgraph4j.CompiledGraph$AsyncNodeGenerator.next(CompiledGraph.java:613)\n",
      "\tat org.bsc.async.AsyncGenerator$WithEmbed.next(AsyncGenerator.java:101)\n",
      "\tat org.bsc.async.InternalIterator.next(AsyncGenerator.java:398)\n",
      "\tat REPL.$JShell$43.testToolsStreamingChat($JShell$43.java:97)\n",
      "\tat REPL.$JShell$45.do_it$($JShell$45.java:14)\n",
      "\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n",
      "\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)\n",
      "\tat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n",
      "\tat java.base/java.lang.reflect.Method.invoke(Method.java:568)\n",
      "\tat org.rapaio.jupyter.kernel.core.java.RapaioExecutionControl.lambda$execute$1(RapaioExecutionControl.java:58)\n",
      "\tat java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)\n",
      "\tat java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)\n",
      "\tat java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)\n",
      "\tat java.base/java.lang.Thread.run(Thread.java:833)\n",
      "Caused by: java.lang.IllegalStateException: no tool found!\n",
      "\tat REPL.$JShell$43.lambda$testToolsStreamingChat$5($JShell$43.java:77)\n",
      "\tat java.base/java.util.Optional.orElseThrow(Optional.java:403)\n",
      "\tat REPL.$JShell$43.lambda$testToolsStreamingChat$6($JShell$43.java:77)\n",
      "\tat org.bsc.langgraph4j.action.AsyncNodeAction.lambda$node_async$0(AsyncNodeAction.java:36)\n",
      "\tat org.bsc.langgraph4j.action.AsyncNodeActionWithConfig.lambda$of$1(AsyncNodeActionWithConfig.java:53)\n",
      "\tat org.bsc.langgraph4j.CompiledGraph$AsyncNodeGenerator.evaluateAction(CompiledGraph.java:517)\n",
      "\t... 14 more\n",
      "\n",
      "StreamingOutput: NodeOutput{node=agent, state={messages=[AiMessage { text = null toolExecutionRequests = [ToolExecutionRequest { id = null, name = \"getCurrencyRate\", arguments = \"{\n",
      "  \"amount\" : \"1000\",\n",
      "  \"from_currency\" : \"USD\",\n",
      "  \"to_currency\" : \"EUR\"\n",
      "}\" }] }]}} \n",
      "ToolExecutionRequest id is null! \n",
      "execute: getCurrencyRate \n",
      "java.lang.IllegalStateException: no tool found! \n",
      "java.util.concurrent.ExecutionException: java.lang.IllegalStateException: no tool found!\n",
      "\tat java.base/java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:396)\n",
      "\tat java.base/java.util.concurrent.CompletableFuture.get(CompletableFuture.java:2073)\n",
      "\tat org.bsc.langgraph4j.CompiledGraph$AsyncNodeGenerator.next(CompiledGraph.java:613)\n",
      "\tat org.bsc.async.AsyncGenerator$WithEmbed.next(AsyncGenerator.java:101)\n",
      "\tat org.bsc.async.InternalIterator.next(AsyncGenerator.java:398)\n",
      "\tat REPL.$JShell$43.testToolsStreamingChat($JShell$43.java:97)\n",
      "\tat REPL.$JShell$45.do_it$($JShell$45.java:14)\n",
      "\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n",
      "\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)\n",
      "\tat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n",
      "\tat java.base/java.lang.reflect.Method.invoke(Method.java:568)\n",
      "\tat org.rapaio.jupyter.kernel.core.java.RapaioExecutionControl.lambda$execute$1(RapaioExecutionControl.java:58)\n",
      "\tat java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)\n",
      "\tat java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)\n",
      "\tat java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)\n",
      "\tat java.base/java.lang.Thread.run(Thread.java:833)\n",
      "Caused by: java.lang.IllegalStateException: no tool found!\n",
      "\tat REPL.$JShell$43.lambda$testToolsStreamingChat$5($JShell$43.java:77)\n",
      "\tat java.base/java.util.Optional.orElseThrow(Optional.java:403)\n",
      "\tat REPL.$JShell$43.lambda$testToolsStreamingChat$6($JShell$43.java:77)\n",
      "\tat org.bsc.langgraph4j.action.AsyncNodeAction.lambda$node_async$0(AsyncNodeAction.java:36)\n",
      "\tat org.bsc.langgraph4j.action.AsyncNodeActionWithConfig.lambda$of$1(AsyncNodeActionWithConfig.java:53)\n",
      "\tat org.bsc.langgraph4j.CompiledGraph$AsyncNodeGenerator.evaluateAction(CompiledGraph.java:517)\n",
      "\t... 14 more\n",
      "\n"
     ]
    },
    {
     "ename": "EvalException",
     "evalue": "java.util.concurrent.ExecutionException: java.lang.IllegalStateException: no tool found!",
     "output_type": "error",
     "traceback": [
      "\u001b[0m\u001b[1m\u001b[31mjava.util.concurrent.CompletionException: java.util.concurrent.ExecutionException: java.lang.IllegalStateException: no tool found!\n\u001b[0m",
      "\u001b[0m\u001b[1m\u001b[34m   at java.base/java.util.concurrent.CompletableFuture.reportJoin(CompletableFuture.java:413)\u001b[0m",
      "\u001b[0m\u001b[1m\u001b[34m   at java.base/java.util.concurrent.CompletableFuture.join(CompletableFuture.java:2118)\u001b[0m",
      "\u001b[0m\u001b[1m\u001b[34m   at org.bsc.async.InternalIterator.next(AsyncGenerator.java:400)\u001b[0m",
      "\u001b[0m\u001b[1m\u001b[34m   at .testToolsStreamingChat(#43:84)\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|    void testToolsStreamingChat( String modelName ) throws Exception {\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|        // Setup streaming model\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|        var model = OllamaStreamingChatModel.builder()\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|                .baseUrl(\"http://localhost:11434\")\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|                .temperature(0.5)\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|                .logRequests(true)\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|                .logResponses(true)\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|                .modelName( modelName )\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|                //.modelName(\"qwen2.5:7b\")\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|                //.modelName(\"llama3.1:latest\")\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|                .build();\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|    \u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|        // Setup tools\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|        var tools = ToolNode.builder()\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|                .specification(new SearchTool())\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|                .build();\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|    \u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|        // Setup serializers\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|        ObjectStreamStateSerializer<MessagesState<ChatMessage>> stateSerializer = new ObjectStreamStateSerializer<>(MessagesState::new);\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|        stateSerializer.mapper()\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|                .register(dev.langchain4j.agent.tool.ToolExecutionRequest.class, new ToolExecutionRequestSerializer())\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|                .register(ChatMessage.class, new ChatMesssageSerializer());\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|    \u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|        // Define graph\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|        NodeAction<MessagesState<ChatMessage>> callModel = state -> {\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|            var generator = StreamingChatGenerator.<MessagesState<ChatMessage>>builder()\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|                    .mapResult(response -> Map.of(\"messages\", response.aiMessage()))\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|                    .startingNode(\"agent\")\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|                    .startingState(state)\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|                    .build();\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|    \u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|            var parameters = ChatRequestParameters.builder()\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|                    .toolSpecifications(tools.toolSpecifications())\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|                    .build();\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|            var request = ChatRequest.builder()\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|                    .messages(state.messages())\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|                    .parameters(parameters)\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|                    .build();\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|    \u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|            model.chat(request, generator.handler());\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|    \u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|            return Map.of(\"_streaming_messages\", generator);\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|        };\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|    \u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|        EdgeAction<MessagesState<ChatMessage>> routeMessage = state -> {\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|            var lastMessage = state.lastMessage()\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|                    .orElseThrow(() -> new IllegalStateException(\"last message not found!\"));\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|    \u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|            if (lastMessage instanceof AiMessage message) {\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|                if (message.hasToolExecutionRequests()) {\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|                    return \"next\";\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|                }\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|            }\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|    \u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|            return \"exit\";\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|        };\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|    \u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|        NodeAction<MessagesState<ChatMessage>> invokeTool = state -> {\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|            var lastMessage = state.lastMessage()\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|                    .orElseThrow(() -> new IllegalStateException(\"last message not found!\"));\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|    \u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|            if (lastMessage instanceof AiMessage lastAiMessage) {\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|                var result = tools.execute(lastAiMessage.toolExecutionRequests(), null)\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|                        .orElseThrow(() -> new IllegalStateException(\"no tool found!\"));\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|    \u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|                return Map.of(\"messages\", result);\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|            }\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|    \u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|            throw new IllegalStateException(\"invalid last message\");\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|        };\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|    \u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|        var workflow = new MessagesStateGraph<>(stateSerializer)\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|                .addNode(\"agent\", node_async(callModel))\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|                .addNode(\"tools\", node_async(invokeTool))\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|                .addEdge(START, \"agent\")\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|                .addConditionalEdges(\"agent\",\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|                        edge_async(routeMessage),\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|                        Map.of(\"next\", \"tools\", \"exit\", END))\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|                .addEdge(\"tools\", \"agent\");\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|    \u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|        var app = workflow.compile();\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|    \u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|        var output = app.stream(Map.of(\"messages\", UserMessage.from(\"How is the weather in New York today?\")));\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|-->     for (var out : output) {\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|            log.info(\"StreamingOutput: {}\", out);\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|        }\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|    }\u001b[0m",
      "\u001b[0m\u001b[1m\u001b[34m   at .(#45:1)\u001b[0m",
      "\u001b[0m\u001b[0m\u001b[1m|--> testToolsStreamingChat(\"llama3.1:latest\");\u001b[0m"
     ]
    }
   ],
   "source": [
    "\n",
    "testToolsStreamingChat(\"llama3.1:latest\");"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Java (rjk 2.2.0)",
   "language": "java",
   "name": "rapaio-jupyter-kernel"
  },
  "language_info": {
   "codemirror_mode": "java",
   "file_extension": ".jshell",
   "mimetype": "text/x-java-source",
   "name": "java",
   "nbconvert_exporter": "script",
   "pygments_lexer": "java",
   "version": "17.0.2+8-86"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
